package htmlsoup

import (
	"bytes"
	"strings"

	"code.google.com/awwwe-browser/data"
)

var writeIndent = 0

// Returns clean html as a slice of bytes.
func (this *SoupNode) Bytes() []byte {
	bfr := bytes.NewBuffer(data.EmptyBytes)
	this.writeHandler(this, bfr)
	return bfr.Bytes()
}

// Returns clean html as a string.
func (this *SoupNode) String() string {
	bfr := bytes.NewBuffer(data.EmptyBytes)
	this.writeHandler(this, bfr)
	return bfr.String()
}

func writeToBufferNil(node *SoupNode, bfr *bytes.Buffer) {
	// do nothing
}

func writeToBufferConditionalComment(node *SoupNode, bfr *bytes.Buffer) {
	// do nothing
}

func writeToBufferDocument(node *SoupNode, bfr *bytes.Buffer) {
	node.writeChildrenToBuffer(bfr)
}

func writeToBufferDocType(node *SoupNode, bfr *bytes.Buffer) {
	bfr.WriteString("<!DOCTYPE html>")
}
func _writeToBufferDocType(node *SoupNode, bfr *bytes.Buffer) {
	bfr.Write(data.DocTypeStartBytes)
	bfr.Write(data.SpaceBytes)
	vBfr := bytes.NewBuffer(data.EmptyBytes)
	vBfr.WriteString(node.value)
	doctype, _, _ := node.parseDocTypeValue(vBfr.Bytes())
	// doctype, value, entityDefs := node.parseValue()
	bfr.Write(doctype)
	/* not used in c#
	   sb.Append("\r\n [\r\n");
	   if (InternalEntityDefs.Length > 0)
	   {
	       sb.Append(InternalEntityDefs);
	       sb.Append("\r\n");
	   }
	   erd = new EntityReferenceData();
	   sb.Append(erd.BuildDocTypeInternalEntitryRefs());
	   sb.Append(" ]");
	*/
	bfr.Write(data.DocTypeEndBytes)
}

func writeToBufferComment(node *SoupNode, bfr *bytes.Buffer) {
	bfr.Write(data.NewLineBytes)
	for j := 0; j < writeIndent; j++ {
		bfr.Write(data.SpaceBytes)
	}
	bfr.Write(data.CommentStartBytes)
	bfr.Write(data.SpaceBytes)
	bfr.WriteString(node.value)
	bfr.Write(data.SpaceBytes)
	bfr.Write(data.CommentEndBytes)
}

func writeToBufferCdata(node *SoupNode, bfr *bytes.Buffer) {
	bfr.Write(data.CDataStartBytes)
	bfr.Write(data.SpaceBytes)
	bfr.WriteString(node.value)
	bfr.Write(data.SpaceBytes)
	bfr.Write(data.CDataEndBytes)
}

func writeToBufferDirective(node *SoupNode, bfr *bytes.Buffer) {
	bfr.Write(data.NewLineBytes)
	for j := 0; j < writeIndent; j++ {
		bfr.Write(data.SpaceBytes)
	}
	bfr.Write(data.DirectiveStartBytes)
	bfr.WriteString(node.name)
	node.attributes.writeToBuffer(bfr)
	bfr.Write(data.SpaceBytes)
	bfr.Write(data.DirectiveEndBytes)
}

func writeToBufferInstruction(node *SoupNode, bfr *bytes.Buffer) {
	bfr.Write(data.NewLineBytes)
	for j := 0; j < writeIndent; j++ {
		bfr.Write(data.SpaceBytes)
	}
	bfr.Write(data.ProcessorInstructionStartBytes)
	bfr.WriteString(node.name)
	bfr.Write(data.SpaceBytes)
	bfr.Write(data.ProcessorInstructionEndBytes)
}

func writeToBufferTag(node *SoupNode, bfr *bytes.Buffer) {
	if node.isValid && node.isOutput {
		bfr.Write(data.NewLineBytes)
		for j := 0; j < writeIndent; j++ {
			bfr.Write(data.SpaceBytes)
		}
		bfr.Write(data.TagStartBytes)
		if len(node.prefix) > 0 {
			bfr.WriteString(node.prefix)
			bfr.Write(data.ColonBytes)
		}
		bfr.WriteString(node.name)
		node.attributes.writeToBuffer(bfr)
		bfr.Write(data.SpaceBytes)
		if node.children.Len() > 0 {
			// finish node opening tag
			bfr.Write(data.TagEndBytes)
			// write the children
			node.writeChildrenToBuffer(bfr)
			// write the closing tag
			bfr.Write(data.NewLineBytes)
			for j := 0; j < writeIndent; j++ {
				bfr.Write(data.SpaceBytes)
			}
			bfr.Write(data.TagStartBytes)
			bfr.Write(data.TagSlashBytes)
			if len(node.prefix) > 0 {
				bfr.WriteString(node.prefix)
				bfr.Write(data.ColonBytes)
			}
			bfr.WriteString(node.name)
			bfr.Write(data.SpaceBytes)
			bfr.Write(data.TagEndBytes)
		} else {
			// no children
			if node.useEndTag == false {
				// opening tag closes itself
				bfr.Write(data.TagSlashBytes)
				bfr.Write(data.TagEndBytes)
			} else {
				// close the opening tag
				bfr.Write(data.TagEndBytes)
				// write the closing tag
				bfr.Write(data.NewLineBytes)
				for j := 0; j < writeIndent; j++ {
					bfr.Write(data.SpaceBytes)
				}
				bfr.Write(data.TagStartBytes)
				bfr.Write(data.TagSlashBytes)
				if len(node.prefix) > 0 {
					bfr.WriteString(node.prefix)
					bfr.Write(data.ColonBytes)
				}
				bfr.WriteString(node.name)
				bfr.Write(data.SpaceBytes)
				bfr.Write(data.TagEndBytes)
			}

		}
	}
}

func writeToBufferText(node *SoupNode, bfr *bytes.Buffer) {
	if len(node.value) > 0 {
		svalue := strings.Trim(node.value, data.WhiteSpaceString)
		if len(svalue) > 0 {
			bfr.Write(data.NewLineBytes)
			for j := 0; j < writeIndent; j++ {
				bfr.Write(data.SpaceBytes)
			}
			bfr.WriteString(node.value)
		}
	}
}

func (this *SoupNode) writeChildrenToBuffer(bfr *bytes.Buffer) {
	writeIndent++
	length := this.children.Len()
	for i := 0; i < length; i++ {
		child := this.children.AtIndex(i)
		child.writeHandler(child, bfr)
	}
	writeIndent--
}

func (dn *SoupNode) parseDocTypeValue(valuebb []byte) (doctype, value, entityDefs []byte) {
	var beginning, start, end int
	var v []byte
	var ibfr, vbfr, dbfr *bytes.Buffer
	dbfr = bytes.NewBuffer(data.EmptyBytes)
	vbfr = bytes.NewBuffer(data.EmptyBytes)
	ibfr = bytes.NewBuffer(data.EmptyBytes)
	v = bytes.TrimSpace(valuebb)
	beginning = bytes.IndexRune(v, data.SpaceRune)
	// get the doctype
	if beginning >= 0 {
		// a space is found so doctype if left of space
		dbfr.Write(v[:beginning])
		beginning++
		// continue right of the space
		v = v[beginning:]
		start = bytes.IndexRune(v, data.LeftSquareBracketRune)
	} else {
		// a space not found so there is no name or no entity defs
		beginning = 0
		start = bytes.IndexRune(v, data.LeftSquareBracketRune)
		if start > 0 {
			// the doctype is left of '[' the entity refs
			dbfr.Write(v[:start])
		} else if start < 0 {
			// there are no entity defs
			// v is the doctype
			dbfr.Write(v)
		}
	}
	// get the entity defs
	if start >= 0 {
		// entity defs follow '[' and continue to ']'
		end = bytes.IndexRune(v, data.RightSquareBracketRune)
		if end < 0 {
			end = len(v)
		}
		ibfr.Write(bytes.Trim(v[start+1:end], data.WhiteSpaceString))
		// got the entity defs now get whats before and after entity defs for the value
		// before '['
		vbfr.Write(v[beginning:start])
		if end+1 < len(v) {
			// after ']'
			vbfr.Write(data.SpaceBytes)
			vbfr.Write(v[end+1:])
		}
		v = bytes.Trim(vbfr.Bytes(), data.WhiteSpaceString)
		vbfr.Truncate(0)
		vbfr.Write(v)
	} else {
		// no internal entity defs
		// trim the whole thing and use it as a value
		vbfr.Write(bytes.Trim(v, data.WhiteSpaceString))
	}
	value = vbfr.Bytes()
	doctype = dbfr.Bytes()
	entityDefs = ibfr.Bytes()
	return
}
